import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;

import weka.associations.FPGrowth;
import weka.associations.FPGrowth.AssociationRule;
import weka.associations.FPGrowth.BinaryItem;
import weka.core.Instances;
 
public class FPGrowthAlgorithm {

	static Map<String,TreeSet<Integer>> itemSets;
	static TreeSet<TreeSet<Integer>> itemSetsOrdered;	
	private String filePath;

	private double lowerBoundMinSupport;
	private double upperBoundMinSupport;
	private double minMetric;
	private int numRulesToFind;
	
	public FPGrowthAlgorithm(double lowerBoundMinSupport, double upperBoundMinSupport, double minMetric, int numRulesToFind, String filePath) {
		this.setLowerBoundMinSupport(lowerBoundMinSupport);
		this.setUpperBoundMinSupport(upperBoundMinSupport);
		this.setMinMetric(minMetric);
		this.setNumRulesToFind(numRulesToFind);
		this.setFilePath(filePath);
		
		itemSets = new TreeMap<String, TreeSet<Integer>>();
		itemSetsOrdered = new TreeSet<TreeSet<Integer>>(new Comparator<TreeSet<Integer>>() {
			@Override
			public int compare(TreeSet<Integer> set1, TreeSet<Integer> set2) {
				if(set1.size() < set2.size())
					return -1;
				else if(set1.size() > set2.size())
					return 1;
				else if (set1.toString().equals(set2.toString())) {
					return 0;
				}
				else {
					Iterator<Integer> set1it = set1.iterator();
					Iterator<Integer> set2it = set2.iterator();
					while(set1it.hasNext()){
						Integer one = set1it.next();
						Integer two = set2it.next();
						int val = one.compareTo(two);
						if(val != 0)
							return val;
					}
					return 0;
				}
				
			}
		});
	}
	
	
	public void compute() throws Exception {
		BufferedReader reader = new BufferedReader(
				new FileReader(this.getFilePath()));
		
		long startTime;
		long endTime;
		
		startTime = System.currentTimeMillis();
		
		Instances data = new Instances(reader);
		reader.close();
		// Make the last attribute be the class
		data.setClassIndex(-1);
		
		FPGrowth fp = new FPGrowth();
		
		// set options
		fp.setLowerBoundMinSupport(this.getLowerBoundMinSupport());
		fp.setUpperBoundMinSupport(this.getUpperBoundMinSupport());
		fp.setMinMetric(this.getMinMetric());
		fp.setNumRulesToFind(this.getNumRulesToFind());
				
		fp.buildAssociations(data);
		
//		int associationsSize = fp.getAssociationRules().size();
		
//		System.out.println("buildAssociations done in " + (System.currentTimeMillis()-startTime) + "ms.");
//		System.out.println(associationsSize + " rules found.");
		
		Map<String,TreeSet<Integer>> tmpItemSets = new TreeMap<String, TreeSet<Integer>>();
		for(AssociationRule rule : fp.getAssociationRules()) {
			//System.out.println(rule);
			TreeSet<Integer> itemset = new TreeSet<Integer>();
			for(BinaryItem item : rule.getPremise()) {
				itemset.add( Integer.parseInt(item.getAttribute().name().trim()) -1 );
			}
			for(BinaryItem item : rule.getConsequence()) {
				itemset.add( Integer.parseInt(item.getAttribute().name().trim()) -1 );
			}
			tmpItemSets.put(itemset.toString(), itemset);
		}

//		System.out.println("rules2itemSets done in " + (System.currentTimeMillis()-startTime) + "ms.");
		
		for(Entry<String, TreeSet<Integer>> entry : tmpItemSets.entrySet()) {
			TreeSet<Integer> itemSet = entry.getValue();
			
			List<TreeSet<Integer>> splitItemSets = new ArrayList<TreeSet<Integer>>();
			splitItemSets.add(itemSet);
			
			while(splitItemSets.size() > 0) {
				TreeSet<Integer> set = splitItemSets.remove(0);
				if(set.size()<=1)
					continue;
				itemSets.put(set.toString(), set);
//				itemSetsOrdered.add(set);
				for (int i = 0; i < set.size(); i++) {
					TreeSet<Integer> newItemSet = new TreeSet<Integer>();
					for (int j = i; j < set.size() + i - 1; j++) {
						newItemSet.add( (Integer) set.toArray()[j % set.size()]);
					}
					if(newItemSet.size() > 0 ){
						if(!itemSets.containsKey(newItemSet.toString())) {
							itemSets.put(newItemSet.toString(), newItemSet);
//							itemSetsOrdered.add(newItemSet);
							splitItemSets.add(newItemSet);
						}	
					}
				}
			}			
		}

//		System.out.print("ItemSets: ");
		System.out.println(itemSets.size());
//		for(TreeSet<Integer> set : itemSetsOrdered) {
//			System.out.println(set);
//		}
		
		endTime = System.currentTimeMillis();
		System.out.println("Calculated in " + (endTime-startTime) + "ms.");
	}



	public double getLowerBoundMinSupport() {
		return this.lowerBoundMinSupport;
	}



	public void setLowerBoundMinSupport(double lowerBoundMinSupport) {
		this.lowerBoundMinSupport = lowerBoundMinSupport;
	}



	public double getUpperBoundMinSupport() {
		return this.upperBoundMinSupport;
	}



	public void setUpperBoundMinSupport(double upperBoundMinSupport) {
		this.upperBoundMinSupport = upperBoundMinSupport;
	}



	public double getMinMetric() {
		return this.minMetric;
	}



	public void setMinMetric(double minMetric) {
		this.minMetric = minMetric;
	}



	public int getNumRulesToFind() {
		return this.numRulesToFind;
	}



	public void setNumRulesToFind(int numRulesToFind) {
		this.numRulesToFind = numRulesToFind;
	}


	public String getFilePath() {
		return filePath;
	}


	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}

}
